<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>分布式锁设计 | 王开琦的个人小站</title><meta name="keywords" content="zookeeper,redis"><meta name="author" content="王开琦"><meta name="copyright" content="王开琦"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="分布式锁要跨进程，多个进程要使用锁的话，JVM里面的锁就不生效了，就需要借助第三方组件实现分布式锁，主流组件有Redis和Zookeeper。">
<meta property="og:type" content="article">
<meta property="og:title" content="分布式锁设计">
<meta property="og:url" content="https://blog.wkq.pub/blog/14-%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E8%AE%BE%E8%AE%A1/index.html">
<meta property="og:site_name" content="王开琦的个人小站">
<meta property="og:description" content="分布式锁要跨进程，多个进程要使用锁的话，JVM里面的锁就不生效了，就需要借助第三方组件实现分布式锁，主流组件有Redis和Zookeeper。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://blog.wkq.pub/media/top_img.jpg">
<meta property="article:published_time" content="2022-04-09T11:18:53.000Z">
<meta property="article:modified_time" content="2022-04-10T15:15:23.974Z">
<meta property="article:author" content="王开琦">
<meta property="article:tag" content="zookeeper">
<meta property="article:tag" content="redis">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://blog.wkq.pub/media/top_img.jpg"><link rel="shortcut icon" href="/media/favicon.ico"><link rel="canonical" href="https://blog.wkq.pub/blog/14-%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E8%AE%BE%E8%AE%A1/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    jQuery: 'https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js',
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/js/jquery.justifiedGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/justifiedGallery/dist/css/justifiedGallery.min.css'
    },
    fancybox: {
      js: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js',
      css: 'https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.css'
    }
  },
  isPhotoFigcaption: true,
  islazyload: true,
  isanchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '分布式锁设计',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-04-10 23:15:23'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if (GLOBAL_CONFIG_SITE.isHome && /iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    document.addEventListener('pjax:complete', detectApple)})(window)</script><meta name="generator" content="Hexo 5.2.0"></head><body><div id="loading-box"><div class="loading-left-bg"></div><div class="loading-right-bg"></div><div class="spinner-box"><div class="configure-border-1"><div class="configure-core"></div></div><div class="configure-border-2"><div class="configure-core"></div></div><div class="loading-word">加载中...</div></div></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "" data-lazy-src="/media/avatar.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data"><div class="data-item is-center"><div class="data-item-link"><a href="/archives/"><div class="headline">文章</div><div class="length-num">15</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/tags/"><div class="headline">标签</div><div class="length-num">17</div></a></div></div><div class="data-item is-center"><div class="data-item-link"><a href="/categories/"><div class="headline">分类</div><div class="length-num">15</div></a></div></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('/media/top_img.jpg')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">王开琦的个人小站</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 归档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">分布式锁设计</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-04-09T11:18:53.000Z" title="发表于 2022-04-09 19:18:53">2022-04-09</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-04-10T15:15:23.974Z" title="更新于 2022-04-10 23:15:23">2022-04-10</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84/">分布式架构</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84/%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81/">分布式锁</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">3.8k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>15分钟</span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h2 id="使用锁解决电商中的超卖问题"><a href="#使用锁解决电商中的超卖问题" class="headerlink" title="使用锁解决电商中的超卖问题"></a>使用锁解决电商中的超卖问题</h2><h3 id="什么是超卖？"><a href="#什么是超卖？" class="headerlink" title="什么是超卖？"></a>什么是超卖？</h3><ul>
<li>举例：某件商品库存数量10件，结果卖出了15件。</li>
<li><p>商品卖出数量超出了库存数量。</p>
</li>
<li><p>超卖导致商家没有商品发货、发货时间延长、买卖双方易发生纠纷。</p>
</li>
</ul>
<div class="tabs" id="超卖现象"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#超卖现象-1">超卖现象一</button></li><li class="tab"><button type="button" data-href="#超卖现象-2">超卖现象二</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="超卖现象-1"><ul>
<li>系统中库存为 1 ，但是产生了两笔订单</li>
<li>卖家在商品发货时，发现只有一件商品，但是有2 笔订单</li>
</ul>
<div class="note primary simple"><p>产生的具体流程：</p>
</div>

<ol>
<li>假设商品只剩下最后一件</li>
<li>A和B同时看到了这个商品，加入购物车，并同时提交订单</li>
<li>超卖产生的现象</li>
</ol>
<div class="note primary simple"><p>产生的原因：</p>
</div>

<ul>
<li>扣库存的动作，在程序中进行，在程序中计算剩余库存，然后把库存数更新到数据库中</li>
<li>并发的场景下，导致库存计算错误</li>
</ul>
<p><img src= "" data-lazy-src="超卖现象1.png" alt=""></p>
<div class="note success simple"><p>解决方法：</p>
</div>

<ul>
<li>扣减库存不在程序中进行，而是通过数据库</li>
<li>想数据库传递库存增量，扣减1个库存，增量为-1</li>
<li>在数据库<code>update</code>语句计算库存，通过<code>update</code>行锁解决并发</li>
</ul>
<div class="note info simple"><p><code>update</code>行锁： 在更新数据的时候给这条数据加上一把锁，在上锁期间，其它<code>update</code>类的语句只要是更新这条记录的都是无法执行的，只有等当前的上锁的<code>update</code>语句执行完成以后，释放锁之后，其它的<code>update</code>语句才可以更新这条记录</p>
</div><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="超卖现象-2"><ul>
<li>系统中库存变为-1</li>
<li>卖家不知所措，询问平台客服</li>
</ul>
<div class="note primary simple"><p>产生的原因：</p>
</div>

<ul>
<li>并发检验库存，造成库存充足的假象；</li>
<li><code>update</code>更新库存,导致库存为负数；</li>
</ul>
<p><img src= "" data-lazy-src="超卖现象2.png" alt=""></p>
<div class="note success simple"><p>解决方法一：</p>
</div>

<ul>
<li>在每次扣减库存之后校验库存，如果库存为负数，就抛出异常（下单失败，库存不足）；</li>
<li>因为会回滚，所以当前这个线程创建订单是不成功的，避免了超卖的现象；</li>
</ul>
<div class="note success simple"><p>解决方法二：</p>
</div>

<ul>
<li><p>校验库存、扣减库存统一加锁，校验库存和扣减库存是一起执行的，要视为一个整体；</p>
</li>
<li><p>使之成为原子性操作；</p>
</li>
<li><p>并发时，只有获得锁的线程才能校验、扣减库存；</p>
</li>
<li><p>扣减库存结束后，释放锁；</p>
</li>
<li><p>确保库存不会扣成负数；</p>
</li>
</ul>
<div class="note info simple"><p>锁的选择：</p>
</div>

<ul>
<li>基于<code>Synchronized</code>锁解决超卖问题（最原始的锁）</li>
<li>基于<code>ReentrantLock</code>锁解决超卖问题（并发包中的锁）</li>
</ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>
<h2 id="分布式锁实现"><a href="#分布式锁实现" class="headerlink" title="分布式锁实现"></a>分布式锁实现</h2><p>分布式锁要跨进程，多个进程要使用锁的话，<code>JVM</code>里面的锁就不生效了，就需要借助第三方组件，主流使用<code>Redis</code>和<code>Zookeeper</code>。</p>
<div class="tabs" id="分布式锁实现"><ul class="nav-tabs"><li class="tab active"><button type="button" data-href="#分布式锁实现-1">基于Zookeeper实现分布式锁</button></li><li class="tab"><button type="button" data-href="#分布式锁实现-2">基于Redis实现分布式锁</button></li><li class="tab"><button type="button" data-href="#分布式锁实现-3">基于数据库实现分布式锁</button></li></ul><div class="tab-contents"><div class="tab-item-content active" id="分布式锁实现-1"><ul>
<li>节点里面可以存储数据；</li>
<li>红色：持久节点；</li>
<li>黄色：瞬时节点，瞬时节点是不可以有子节点的；有序是指创建瞬时节点的时候，名称是按瞬时节点排序的；</li>
</ul>
<div class="note info simple"><p>瞬时节点不可再有子节点，会话结束后（重启等）瞬时节点自动消失</p>
</div>

<p><img src= "" data-lazy-src="Zookeeper的数据结构.png" alt=""></p>
<div class="note default simple"><p><a href="https://blog.wkq.pub/blog/18-分布式架构-分布式协调-Zookeeper简介与环境搭建/">Zookeeper安装配置链接</a></p>
</div>

<div class="note info simple"><p><code>Zookeeper</code>的观察器：可以检测<code>Zookeeper</code>里某个节点的变化，比如节点存在和不存在，节点数据发生变化和子节点发生变化等；如果有变化，观察期会立即通知到客户端</p>
<ul>
<li>可设置观察器的3个方法：<code>getData()</code>、<code>getChildren()</code>、<code>exists()</code>；</li>
<li>节点数据发生变化，发送给客户端；</li>
<li>观察器只能监控一次，再监控需重新设置；</li>
</ul>
</div>

<div class="note primary simple"><p>实现原理：</p>
</div>

<ul>
<li>利用<code>Zookeeper</code>的瞬时有序节点的特性</li>
<li>多线程并发创建瞬时节点时，得到有序的序列；</li>
<li>序号最小的线程获得锁；</li>
<li>其它线程则监听自己序号的前一个序号；</li>
<li>前一个线程执行完成，删除自己序号的节点；</li>
<li>下一个序号的线程得到通知，继续执行；</li>
<li>以此类推……；</li>
<li>创建节点时，已经确定了线程的执行顺序；</li>
</ul>
<p><img src= "" data-lazy-src="Zookeeper基于瞬时节点实现分布式锁原理图解.png" alt=""></p>
<div class="note primary simple"><p>根据上述原理，编写<code>Zookeeper</code>分布式锁：</p>
</div>

<ol>
<li><p>创建<code>springboot</code>项目，引入依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.zookeeper<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>zookeeper<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.8.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>创建<code>ZKLock</code>工具类提供获取锁的功能。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> org.apache.zookeeper.*;</span><br><span class="line"><span class="keyword">import</span> org.apache.zookeeper.data.Stat;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.StandardCharsets;</span><br><span class="line"><span class="keyword">import</span> java.util.Collections;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZkLock</span> <span class="keyword">implements</span> <span class="title">Watcher</span>, <span class="title">AutoCloseable</span>/*实现这个接口可以让类实现自动关闭*/ </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> ZooKeeper zooKeeper;</span><br><span class="line">    <span class="keyword">private</span> String znode;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ZkLock</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.zooKeeper = <span class="keyword">new</span> ZooKeeper(<span class="string">&quot;zk1:2181,zk2:2181,zk3:2181,zk4:2181,zk5:2181&quot;</span>, <span class="number">10000</span>, <span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">getLock</span><span class="params">(String businessCode)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//1.判断业务根节点是否存在，不存在就要创建业务根节点</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Stat stat = zooKeeper.exists(<span class="string">&quot;/&quot;</span> + businessCode, <span class="keyword">false</span>);</span><br><span class="line">            <span class="keyword">if</span> (stat == <span class="keyword">null</span>) &#123;</span><br><span class="line">                zooKeeper.create(<span class="string">&quot;/&quot;</span> + businessCode,<span class="comment">/*节点路径*/</span></span><br><span class="line">                        businessCode.getBytes()<span class="comment">/*节点初始数据*/</span>,</span><br><span class="line">                        ZooDefs.Ids.OPEN_ACL_UNSAFE<span class="comment">/*节点权限*/</span>,</span><br><span class="line">                        CreateMode.PERSISTENT<span class="comment">/*节点类型 持久节点；瞬时节点*/</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//2.创建瞬时有序节点  /order/order_0000001    下划线后面会自动跟序号</span></span><br><span class="line">            znode = zooKeeper.create(<span class="string">&quot;/&quot;</span> + businessCode + <span class="string">&quot;/&quot;</span> + businessCode + <span class="string">&quot;_&quot;</span>,</span><br><span class="line">                    businessCode.getBytes(),</span><br><span class="line">                    ZooDefs.Ids.OPEN_ACL_UNSAFE,</span><br><span class="line">                    CreateMode.EPHEMERAL_SEQUENTIAL<span class="comment">/*创建瞬时有序节点*/</span>);</span><br><span class="line">            <span class="comment">//3.判断当前瞬时节点是不是 序号最小的,获取业务根节点下的所有子节点并排序</span></span><br><span class="line">            List&lt;String&gt; childrenNode = zooKeeper.getChildren(<span class="string">&quot;/&quot;</span> + businessCode, <span class="keyword">false</span>);</span><br><span class="line">            <span class="comment">//子节点排序</span></span><br><span class="line">            Collections.sort(childrenNode);</span><br><span class="line">            <span class="comment">//获取序号最小的子节点（第一个子节点）</span></span><br><span class="line">            String firstNode = childrenNode.get(<span class="number">0</span>);</span><br><span class="line">            <span class="comment">//4.如果创建的节点是第一个子节点，则获得锁，如果不是则要监听前一个节点</span></span><br><span class="line">            <span class="keyword">if</span> (znode.endsWith(firstNode)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//不是第一个子节点，则监听前一个节点</span></span><br><span class="line">            String lastNode = firstNode;</span><br><span class="line">            <span class="keyword">for</span> (String node : childrenNode) &#123;</span><br><span class="line">                <span class="keyword">if</span> (znode.endsWith(node)) &#123;</span><br><span class="line">                    zooKeeper.exists(<span class="string">&quot;/&quot;</span> + businessCode + <span class="string">&quot;/&quot;</span> + lastNode, <span class="keyword">true</span>);<span class="comment">//监听节点，如果节点消失了，会得到通知</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    lastNode = node;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line">                <span class="comment">//等待前面节点消失时唤醒</span></span><br><span class="line">                wait();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (KeeperException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 释放锁：直接删除节点</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        zooKeeper.delete(znode, -<span class="number">1</span>);</span><br><span class="line">        zooKeeper.close();</span><br><span class="line">        log.info(<span class="string">&quot;我已经释放了锁&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 当节点消失后，就会进入到此方法中</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> watchedEvent</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">process</span><span class="params">(WatchedEvent watchedEvent)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (watchedEvent.getType() == Event.EventType.NodeDeleted) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line">                notify();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建<code>ZKController</code>测试<code>ZKLock</code>工具类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ZookeeperController</span> </span>&#123;</span><br><span class="line">    <span class="meta">@GetMapping(&quot;zkLock&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">zookeeperLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">&quot;我进入了方法&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> (ZkLock zkLock = <span class="keyword">new</span> ZkLock()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (zkLock.getLock(<span class="string">&quot;order&quot;</span>)) &#123;</span><br><span class="line">                log.info(<span class="string">&quot;我获得了锁&quot;</span>);</span><br><span class="line">                Thread.sleep(<span class="number">15000</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        log.info(<span class="string">&quot;方法执行完成&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;方法执行完成&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>在8000、8001、8002、8003端口上启动这一个<code>springboot</code>项目，分别访问<code>http://localhost:{端口号}/zkLock</code>，可以发现这四个线程是依次获得锁去执行的。打开<code>prettyZoo</code>连接<code>Zookeeper</code>。当释放锁之后，瞬时节点会消失。</p>
<p><img src= "" data-lazy-src="Zookeeper分布式锁节点名称.png" alt=""></p>
</li>
</ol><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分布式锁实现-2"><div class="note primary simple"><p>实现原理：</p>
</div>

<ul>
<li><p>获取锁的<code>Redis</code>命令</p>
</li>
<li><p><code>SET resource_name my_random_value NX PX 30000</code>；</p>
</li>
<li><code>resource_name</code>：资源名称，可根据不同的业务区分不同的锁；</li>
<li><code>my_random_value</code>:随机值（最好用<code>UUID</code>），每个线程的随机值都不同，用于释放锁时的校验；</li>
<li><code>NX</code>：<code>key</code>不存在时设置成功，<code>key</code>存在则设置不成功(<code>Redis</code>能作为分布式锁，主要就是利用了这个特性)；</li>
<li><code>PX</code>:自动失效时间，出现异常情况，锁可以过期失效；</li>
<li>利用了<code>NX</code>的原子性,保证多个线程并发时，只有一个线程设置成功；</li>
<li>设置成功即获得锁，可以执行后续的业务处理；</li>
<li>如果出现了异常，过了锁的有效期，锁自动释放；</li>
<li>释放锁采用<code>Redis</code>的<code>delete</code>命令；</li>
<li>释放锁时校验之前设置的随机数，相同才能释放；</li>
<li>释放锁的<code>LUA</code>脚本，因为<code>delete</code>命令并没有提供值校验这一功能；</li>
<li>在<code>Redis</code>集群中，会根据<code>key</code>做分片</li>
</ul>
<div class="note primary simple"><p>释放锁的<code>LUA</code>脚本：</p>
</div>

<p>不做值校验有可能释放了别的线程所加的锁，比如锁过期的情况</p>
<figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> redis.call(<span class="string">&quot;get&quot;</span>,KEYS[<span class="number">1</span>])==ARGV[<span class="number">1</span>] <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">return</span> redis.call(<span class="string">&quot;del&quot;</span>,KEYS[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p><img src= "" data-lazy-src="释放锁的值校验的原理图解.png" alt="释放锁需要值校验的原理"></p>
<div class="note primary simple"><p>根据上述原理，编写<code>Redis</code>分布式锁：</p>
</div> 

<ol>
<li><p>创建<code>springboot</code>项目，并引入<code>redis客户端</code>依赖；</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span>	</span><br></pre></td></tr></table></figure>
</li>
<li><p>编写<code>Redis</code>锁工具类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisLock</span> <span class="keyword">implements</span> <span class="title">AutoCloseable</span>/*实现这个接口可以让类实现自动关闭*/</span>&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line">    <span class="keyword">private</span> String key;</span><br><span class="line">    <span class="keyword">private</span> String value;</span><br><span class="line">    <span class="comment">//单位：秒</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> expireTime;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">RedisLock</span><span class="params">(RedisTemplate redisTemplate, String key, <span class="keyword">int</span> expireTime)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.redisTemplate = redisTemplate;</span><br><span class="line">        <span class="keyword">this</span>.key = key;</span><br><span class="line">        <span class="keyword">this</span>.expireTime = expireTime;</span><br><span class="line">        <span class="keyword">this</span>.value = UUID.randomUUID().toString();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取分布式锁</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 为true获取成功</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">getLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        RedisCallback&lt;Boolean&gt; redisCallback = connection -&gt; &#123;</span><br><span class="line">            <span class="comment">//设置 NX</span></span><br><span class="line">            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();</span><br><span class="line">            <span class="comment">//设置过期时间</span></span><br><span class="line">            Expiration expiration = Expiration.seconds(expireTime);</span><br><span class="line">            <span class="comment">//序列化key</span></span><br><span class="line">            <span class="keyword">byte</span>[] redisKey = redisTemplate.getKeySerializer().serialize(key);</span><br><span class="line">            <span class="comment">//序列化value</span></span><br><span class="line">            <span class="keyword">byte</span>[] redisValue = redisTemplate.getValueSerializer().serialize(value);</span><br><span class="line">            <span class="comment">//执行setnx操作</span></span><br><span class="line">            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">//获取分布式锁</span></span><br><span class="line">        Boolean lock = (Boolean) redisTemplate.execute(redisCallback);</span><br><span class="line">        <span class="keyword">return</span> lock;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 释放分布式锁</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 为true获取成功</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">unLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        String script = <span class="string">&quot;if redis.call(\&quot;get\&quot;,KEYS[1])==ARGV[1] then\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    return redis.call(\&quot;del\&quot;,KEYS[1])\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;else\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;    return 0\n&quot;</span> +</span><br><span class="line">                <span class="string">&quot;end&quot;</span>;</span><br><span class="line">        RedisScript&lt;Boolean&gt; redisScript = RedisScript.of(script, Boolean.class);</span><br><span class="line">        List keys = Arrays.asList(key);</span><br><span class="line">        Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 自动关闭，需要try()-catch进行配合</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        unLock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>编写<code>controller</code> ，并在多个端口启动测试<code>controller</code>;</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;redisLock&quot;)</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> String <span class="title">redisLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      log.info(<span class="string">&quot;我进入了方法&quot;</span>);</span><br><span class="line">   </span><br><span class="line">      String key = <span class="string">&quot;redisKey&quot;</span>;</span><br><span class="line">      <span class="comment">//获取分布式锁</span></span><br><span class="line">      <span class="keyword">try</span> (RedisLock redisLock = <span class="keyword">new</span> RedisLock(redisTemplate, key, <span class="number">30</span>)) &#123;<span class="comment">/*在方法结束的时候会自动释放括号中的资源*/</span></span><br><span class="line">          Boolean lock = redisLock.getLock();</span><br><span class="line">          <span class="keyword">if</span> (lock) &#123;</span><br><span class="line">              log.info(<span class="string">&quot;我进入了锁！！&quot;</span>);</span><br><span class="line">              <span class="comment">//模拟处理过程</span></span><br><span class="line">              Thread.sleep(<span class="number">15000</span>);</span><br><span class="line">          &#125;</span><br><span class="line">      &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">          e.printStackTrace();</span><br><span class="line">      &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">          e.printStackTrace();</span><br><span class="line">      &#125;</span><br><span class="line">      log.info(<span class="string">&quot;方法执行完成&quot;</span>);</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&quot;方法执行完成&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">   </span><br></pre></td></tr></table></figure>
</li>
</ol>
<div class="note success simple"><p>8080端口的运行结果：</p>
</div>

<figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[nio-8080-exec-3] c.e.d.controller.RedisLockController     : 我进入了方法</span><br><span class="line">[nio-8080-exec-3] c.e.d.controller.RedisLockController     : 我进入了锁！！</span><br><span class="line">[nio-8080-exec-3] c.e.d.controller.RedisLockController     : 释放锁的结果：true</span><br><span class="line">[nio-8080-exec-3] c.e.d.controller.RedisLockController     : 方法执行完成</span><br></pre></td></tr></table></figure>
<div class="note success simple"><p>8081端口的运行结果：</p>
</div>

<figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[nio-8081-exec-1] c.e.d.controller.RedisLockController     : 我进入了方法</span><br><span class="line">[nio-8081-exec-1] c.e.d.controller.RedisLockController     : 方法执行完成</span><br></pre></td></tr></table></figure>
<div class="note primary simple"><p>定时任务集群部署（如Spring Task），任 务重复执行 ，利用分布式锁解决重复执行的问题：</p>
</div>

<p><img src= "" data-lazy-src="定时任务集群部署架构图.png" alt="定时任务集群部署架构图"></p>
<div class="note info simple"><p>场景描述：如上图，定时任务服务部署 了两台，某一时刻任务A开始执行，由于部署了两台，所以会同时运行两个任务A，这时候任务A就重复执行了（比如定时发送短信服务）</p>
</div>  

<p><img src= "" data-lazy-src="使用分布式锁解决定时任务重复执行问题的架构图.png" alt="使用分布式锁解决定时任务重复执行问题的架构图"></p>
<div class="note info simple"><p><code>SpringBoot</code>项目，只要加上<code>@EnableScheduling</code>注解就可以创建定时任务    </p>
</div>  

<p>启动多个进程 ,执行定时任务</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SchedulerService</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;0/5 * * * * ?&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sendSms</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> (RedisLock redisLock = <span class="keyword">new</span> RedisLock(redisTemplate, <span class="string">&quot;autoSms&quot;</span>, <span class="number">30</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (redisLock.getLock()) &#123;</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>);<span class="comment">//任务执行时间太短的话，第一个线程释放锁，第二个线程会立马获取到锁</span></span><br><span class="line">                log.info(<span class="string">&quot;&#123;&#125;向138xxxxxxxx发送短信！&quot;</span>, <span class="keyword">new</span> Date());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div><div class="tab-item-content" id="分布式锁实现-3"><div class="note primary simple"><p>基于数据库实现分布式锁的步骤：</p>
</div>

<ul>
<li>多个进程、多个线程访问共同组件数据库；</li>
<li>通过 <code>select……for update</code>访问同一条数据，这条语句在检索数据的时候，会将检索出的数据给加上锁。；</li>
<li><code>for……update</code>锁定数据，其它线程只能等待；</li>
</ul>
<div class="note primary simple"><p>基于数据库设计分布式锁的优缺点：</p>
</div>

<ul>
<li>优点：简单方便、易于理解、易于操作；</li>
<li>缺点：并发量大时，对数据库压力较大；</li>
<li>建议：作为锁的数据库与业务数据库分开</li>
</ul><button type="button" class="tab-to-top" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div></div>
<h2 id="Curator分布式锁"><a href="#Curator分布式锁" class="headerlink" title="Curator分布式锁"></a>Curator分布式锁</h2><blockquote>
<p>Apache Curator is a Java/JVM client library for <a target="_blank" rel="noopener" href="https://zookeeper.apache.org/">Apache ZooKeeper</a>, a distributed coordination service. It includes a highlevel API framework and utilities to make using Apache ZooKeeper much easier and more reliable. It also includes recipes for common use cases and extensions such as service discovery and a Java 8 asynchronous DSL.</p>
</blockquote>
<ul>
<li>引入<code>curator</code>客户端，<code>curator</code>是基于<code>Zookeeper</code>的，是<code>zookeeper</code>客户端的升级版本</li>
<li><code>curator</code>已经实现了分布式锁的方法，直接调用即可</li>
</ul>
<div class="note primary simple"><p>使用<code>curator</code>实现分布式锁：</p>
</div>
<ol>
<li><p>创建<code>springboot</code>项目，引入<code>Curator</code>依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.curator<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>curator-recipes<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>4.2.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>创建获取连接的方法并注入容器</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GetCuratorFramework</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean(initMethod = &quot;start&quot;, destroyMethod = &quot;close&quot;)</span><span class="comment">//指定初始化方法和销毁方法</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> CuratorFramework <span class="title">getConnection</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        RetryPolicy retryPolicy = <span class="keyword">new</span> ExponentialBackoffRetry(<span class="number">1000</span>, <span class="number">3</span>);</span><br><span class="line">        CuratorFramework client = CuratorFrameworkFactory.newClient(<span class="string">&quot;zk1:2181,zk2:2181,zk3:2181,zk4:2181,zk5:2181&quot;</span>, retryPolicy);</span><br><span class="line">        <span class="keyword">return</span> client;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>在<code>controller</code>使用多个进程进行测试，多个进程依然是顺序执行的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> CuratorFramework client;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping(&quot;curatorLock&quot;)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">curatorLock</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    log.info(<span class="string">&quot;我进入了方法&quot;</span>);</span><br><span class="line">    InterProcessMutex lock = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        lock = <span class="keyword">new</span> InterProcessMutex(client, <span class="string">&quot;/order&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (lock.acquire(<span class="number">30</span>, TimeUnit.SECONDS)) &#123;</span><br><span class="line">            log.info(<span class="string">&quot;我获得了锁&quot;</span>);</span><br><span class="line">            Thread.sleep(<span class="number">10000</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">//释放锁</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">null</span>!=lock) lock.release();</span><br><span class="line">    &#125;</span><br><span class="line">    log.info(<span class="string">&quot;方法执行完成&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;方法执行完成&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="Redisson分布式锁"><a href="#Redisson分布式锁" class="headerlink" title="Redisson分布式锁"></a>Redisson分布式锁</h2><p><code>Redisson</code>重新实现了<code>JUC</code>包下的工具类以及<code>List</code>、<code>HashMap</code>等集合类，让这些工具类可以跨<code>JVM</code>使用。<code>Redisson</code>是<code>Redis</code>客户端的升级版本，提供了分布式锁，只需调用。</p>
<div class="note primary simple"><p>在<code>springboot</code>项目中使用<code>Redisson</code>实现分布式锁：</p>
</div>
<ol>
<li><p>创建Spring boot项目，引入 <code>Resisson</code>的<code>starter</code>依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.redisson<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>redisson-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.17.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>进行<code>Redisson</code>与<code>Redis</code>的配置</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">host:</span> <span class="string">localhost</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>在<code>controller</code>中编写测试代码进行多进程测试</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedissonLockController</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedissonClient redissonClient;</span><br><span class="line">    <span class="meta">@GetMapping(&quot;redissonLock&quot;)</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">redissonLock</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        RLock rLock = redissonClient.getLock(<span class="string">&quot;order&quot;</span>);</span><br><span class="line">        log.info(<span class="string">&quot;我进入了方法！！&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            rLock.lock(<span class="number">30</span>, TimeUnit.SECONDS);</span><br><span class="line">            log.info(<span class="string">&quot;我获得了锁！！！&quot;</span>);</span><br><span class="line">            Thread.sleep(<span class="number">10000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            log.info(<span class="string">&quot;我释放了锁&quot;</span>);</span><br><span class="line">            rLock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">        log.info(<span class="string">&quot;方法执行完成&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;方法执行完毕&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="分布式锁的对比"><a href="#分布式锁的对比" class="headerlink" title="分布式锁的对比"></a>分布式锁的对比</h2><div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:center">方式</th>
<th style="text-align:center">优点</th>
<th style="text-align:center">缺点</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">数据库</td>
<td style="text-align:center">实现简单、已于理解</td>
<td style="text-align:center">对数据库压力大</td>
</tr>
<tr>
<td style="text-align:center">Redis</td>
<td style="text-align:center">易于理解</td>
<td style="text-align:center">自己实现、不支持阻塞</td>
</tr>
<tr>
<td style="text-align:center">Zookeeper</td>
<td style="text-align:center">支持阻塞</td>
<td style="text-align:center">自己实现，需理解Zookeeper、程序复杂</td>
</tr>
<tr>
<td style="text-align:center">Curator</td>
<td style="text-align:center">提供锁的方法</td>
<td style="text-align:center">依赖Zookeeper，强一致，对集群有压力</td>
</tr>
<tr>
<td style="text-align:center">Redisson</td>
<td style="text-align:center">提供锁的方法、可阻塞</td>
<td style="text-align:center">无</td>
</tr>
</tbody>
</table>
</div>
<ul>
<li>不推荐自己编写的分布式锁</li>
<li>推荐<code>Redisson</code>和<code>Curator</code>实现的分布式锁</li>
</ul>
</article><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/zookeeper/">zookeeper</a><a class="post-meta__tags" href="/tags/redis/">redis</a></div><div class="post_share"><div class="social-share" data-image="/media/top_img.jpg" data-sites="wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/social-share.js/dist/js/social-share.min.js" defer></script></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src= "" data-lazy-src="/media/avatar.jpg" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">王开琦</div><div class="author-info__description">今日事今日毕</div></div><div class="card-info-data"><div class="card-info-data-item is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">15</div></a></div><div class="card-info-data-item is-center"><a href="/tags/"><div class="headline">标签</div><div class="length-num">17</div></a></div><div class="card-info-data-item is-center"><a href="/categories/"><div class="headline">分类</div><div class="length-num">15</div></a></div></div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E9%94%81%E8%A7%A3%E5%86%B3%E7%94%B5%E5%95%86%E4%B8%AD%E7%9A%84%E8%B6%85%E5%8D%96%E9%97%AE%E9%A2%98"><span class="toc-number">1.</span> <span class="toc-text">使用锁解决电商中的超卖问题</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E8%B6%85%E5%8D%96%EF%BC%9F"><span class="toc-number">1.1.</span> <span class="toc-text">什么是超卖？</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0"><span class="toc-number">2.</span> <span class="toc-text">分布式锁实现</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Curator%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="toc-number">3.</span> <span class="toc-text">Curator分布式锁</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Redisson%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="toc-number">4.</span> <span class="toc-text">Redisson分布式锁</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AF%B9%E6%AF%94"><span class="toc-number">5.</span> <span class="toc-text">分布式锁的对比</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/blog/3-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98-%E7%89%B9%E5%AE%9A%E8%AF%AD%E5%8F%A5%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E8%B0%83%E4%BC%98/" title="3.数据库-数据库监控与调优-特定语句的原理和调优"><img src= "" data-lazy-src="/media/top_img.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="3.数据库-数据库监控与调优-特定语句的原理和调优"/></a><div class="content"><a class="title" href="/blog/3-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98-%E7%89%B9%E5%AE%9A%E8%AF%AD%E5%8F%A5%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E8%B0%83%E4%BC%98/" title="3.数据库-数据库监控与调优-特定语句的原理和调优">3.数据库-数据库监控与调优-特定语句的原理和调优</a><time datetime="2022-04-22T08:14:26.000Z" title="发表于 2022-04-22 16:14:26">2022-04-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/3-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%B0%83%E4%BC%98%E7%90%86%E8%AE%BA/" title="数据库调优理论"><img src= "" data-lazy-src="/media/top_img.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="数据库调优理论"/></a><div class="content"><a class="title" href="/blog/3-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%B0%83%E4%BC%98%E7%90%86%E8%AE%BA/" title="数据库调优理论">数据库调优理论</a><time datetime="2022-04-22T08:13:31.000Z" title="发表于 2022-04-22 16:13:31">2022-04-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/3-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98-%E6%85%A2%E6%9F%A5%E8%AF%A2%E5%8F%91%E7%8E%B0%E4%B8%8E%E5%88%86%E6%9E%90/" title="慢查询发现与分析"><img src= "" data-lazy-src="/media/top_img.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="慢查询发现与分析"/></a><div class="content"><a class="title" href="/blog/3-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98-%E6%85%A2%E6%9F%A5%E8%AF%A2%E5%8F%91%E7%8E%B0%E4%B8%8E%E5%88%86%E6%9E%90/" title="慢查询发现与分析">慢查询发现与分析</a><time datetime="2022-04-22T08:12:32.000Z" title="发表于 2022-04-22 16:12:32">2022-04-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/1-Java-JVM%E8%B0%83%E4%BC%98%E7%90%86%E8%AE%BA/" title="JVM 调优理论"><img src= "" data-lazy-src="/media/top_img.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="JVM 调优理论"/></a><div class="content"><a class="title" href="/blog/1-Java-JVM%E8%B0%83%E4%BC%98%E7%90%86%E8%AE%BA/" title="JVM 调优理论">JVM 调优理论</a><time datetime="2022-04-21T06:40:41.000Z" title="发表于 2022-04-21 14:40:41">2022-04-21</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/20-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84-%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86Eureka/" title="服务治理Eureka"><img src= "" data-lazy-src="/media/top_img.jpg" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="服务治理Eureka"/></a><div class="content"><a class="title" href="/blog/20-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84-%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86-%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86Eureka/" title="服务治理Eureka">服务治理Eureka</a><time datetime="2022-04-13T13:18:27.000Z" title="发表于 2022-04-13 21:18:27">2022-04-13</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="footer_custom_text">Copyright© 2022 blog.wkq.pub <span>陇ICP备2021003596号</span></a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><div class="search-dialog__title" id="local-search-title">本地搜索</div><div id="local-input-panel"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div></div><hr/><div id="local-search-results"></div><span class="search-close-button"><i class="fas fa-times"></i></span></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="/js/search/local-search.js"></script><script>var preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',preloader.endLoading())</script><div class="js-pjax"><script>if (document.getElementsByClassName('mermaid').length) {
  if (window.mermaidJsLoad) mermaid.init()
  else {
    getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(() => {
      window.mermaidJsLoad = true
      mermaid.initialize({
        theme: 'default',
      })
      true && mermaid.init()
    })
  }
}</script></div><div class="aplayer no-destroy" data-id="7675104902" data-server="tencent" data-type="playlist" data-fixed="true" data-mini="true" data-listFolded="false" data-order="random" data-preload="none" data-autoplay="true" muted></div><canvas class="fireworks" mobile="true"></canvas><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/fireworks.min.js"></script><script defer="defer" id="fluttering_ribbon" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-fluttering-ribbon.min.js"></script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdelivr.net/gh/metowolf/MetingJS@1.2/dist/Meting.min.js"></script><script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script><script>let pjaxSelectors = [
  'title',
  '#config-diff',
  '#body-wrap',
  '#rightside-config-hide',
  '#rightside-config-show',
  '.js-pjax'
]

if (false) {
  pjaxSelectors.unshift('meta[property="og:image"]', 'meta[property="og:title"]', 'meta[property="og:url"]')
}

var pjax = new Pjax({
  elements: 'a:not([target="_blank"])',
  selectors: pjaxSelectors,
  cacheBust: false,
  analytics: false,
  scrollRestoration: false
})

document.addEventListener('pjax:send', function () {

  // removeEventListener scroll 
  window.removeEventListener('scroll', window.tocScrollFn)
  window.removeEventListener('scroll', scrollCollect)

  typeof preloader === 'object' && preloader.initLoading()
  
  if (window.aplayers) {
    for (let i = 0; i < window.aplayers.length; i++) {
      if (!window.aplayers[i].options.fixed) {
        window.aplayers[i].destroy()
      }
    }
  }

  typeof typed === 'object' && typed.destroy()

  //reset readmode
  const $bodyClassList = document.body.classList
  $bodyClassList.contains('read-mode') && $bodyClassList.remove('read-mode')

})

document.addEventListener('pjax:complete', function () {
  window.refreshFn()

  document.querySelectorAll('script[data-pjax]').forEach(item => {
    const newScript = document.createElement('script')
    const content = item.text || item.textContent || item.innerHTML || ""
    Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
    newScript.appendChild(document.createTextNode(content))
    item.parentNode.replaceChild(newScript, item)
  })

  GLOBAL_CONFIG.islazyload && window.lazyLoadInstance.update()

  typeof chatBtnFn === 'function' && chatBtnFn()
  typeof panguInit === 'function' && panguInit()

  // google analytics
  typeof gtag === 'function' && gtag('config', '', {'page_path': window.location.pathname});

  // baidu analytics
  typeof _hmt === 'object' && _hmt.push(['_trackPageview',window.location.pathname]);

  typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting()

  // Analytics
  if (false) {
    MtaH5.pgv()
  }

  // prismjs
  typeof Prism === 'object' && Prism.highlightAll()

  typeof preloader === 'object' && preloader.endLoading()
})

document.addEventListener('pjax:error', (e) => {
  if (e.request.status === 404) {
    pjax.loadUrl('/404.html')
  }
})</script></div><script src="/live2d_models/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2d_models/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"jsonPath":"/live2d_models/assets/penchan.model.json"},"display":{"position":"left","width":100,"height":150,"hOffset":-10,"vOffset":40},"mobile":{"show":true},"react":{"opacity":0.7},"log":false});</script></body></html>